iT邦幫忙

2022 iThome 鐵人賽

DAY 23
2
Modern Web

今天我想來在 Angular 應用程式上加上測試保護系列 第 23

Day 23 - 端對端測試 - Cypress 別名與自訂命令

  • 分享至 

  • xImage
  •  

前言

端對端測試是從使用者的角度進撰寫,因而會在不同的案例中重覆撰寫相同的程式碼,今天就來說明在 Cypress 如何降低重覆程式碼的比例。

使用 Cypress 別名

在登入作業的測試程式內,帳號不存在、登入成功與失敗三個案例,我們都會輸入帳號與密碼,並且點選登入按鈕。所以在這些案例中會重覆撰寫取得頁面元素以及輸入與點選的動作。

it('當輸入不存在帳號, 應顯示錯誤訊息, 且無法按下登入按鈕', () => {
  cy.fixture('users/id-not-exists').then(({ id, password }) => {
    cy.get('button.mat-icon-button').contains('login').click();
    cy.get('input[type=text]').type(id).blur();
    cy.get('button').contains('登入').parent().should('be.disabled');
    cy.get('mat-error').should('exist').and('have.text', '此帳號不存在');
  });
});

it('當輸入錯誤帳號密碼, 應登入失敗', () => {
  cy.fixture('users/login-failed').then(({ id, password }) => {
    cy.get('button.mat-icon-button').contains('login').click();
    cy.get('input[type=text]').type(id);
    cy.get('input[type=password]').type(password);
    cy.get('button').contains('登入').parent().click({ force: true }).should('not.be.disabled');
    cy.get('.mat-snack-bar-container').should('exist').and('have.text', '登入失敗');
  });
});

it('當輸入正確帳號密碼, 應登入成功', () => {
  cy.fixture('users/login-success').then(({ id, password }) => {
    cy.get('button.mat-icon-button').contains('login').click();
    cy.get('input[type=text]').type(id);
    cy.get('input[type=password]').type(password);
    cy.get('button').contains('登入').parent().click({ force: true }).should('not.be.disabled');
    cy.get('.mat-snack-bar-container').should('exist').and('have.text', '登入成功');
  });
});

此時,可以利用 Cypress 所提供的 as() 方法,在取得頁面元素後給予一個別名。

describe('登入作業', () => {
  beforeEach(() => {
    cy.get('input[type=text]').as('id');
    cy.get('input[type=password]').as('password');
    cy.get('button').contains('登入').parent().as('loginButton');
  });

  it('當輸入不存在帳號, 應顯示錯誤訊息, 且無法按下登入按鈕', () => {
    cy.fixture('users/id-not-exists').then(({ id, password }) => {
      cy.get('@id').type(id).blur();
      cy.get('@loginButton').should('be.disabled');
      cy.get('mat-error').should('exist').and('have.text', '此帳號不存在');
    });
  });

  it('當輸入錯誤帳號密碼, 應登入失敗', () => {
    cy.fixture('users/login-failed').then(({ id, password }) => {
      cy.get('@id').type(id);
      cy.get('@password').type(password);
      cy.get('@loginButton').click({ force: true }).should('not.be.disabled');
      cy.get('.mat-snack-bar-container').should('exist').and('have.text', '登入失敗');
    });
  });

  it('當輸入正確帳號密碼, 應登入成功', () => {
    cy.fixture('users/login-success').then(({ id, password }) => {
      cy.get('@id').type(id);
      cy.get('@password').type(password);
      cy.get('@loginButton').click({ force: true }).should('not.be.disabled');
      cy.get('.mat-snack-bar-container').should('exist').and('have.text', '登入成功');
    });
  });
});

如上面程式所示,在 beforeEach 生命週期方法下,我們可以將帳號、密碼與登入按鈕的元素取得,然後利用 as() 設定一個別名。如此一來,就可以在各測試案例下,透過 @別名 的方式直接取得對應的頁面元素,來讓頁面元素的統一在 beforeEach 定義。

使用 Cypress 自訂命令

先前文章中,我們提到了在 Jasmine 中會利用 Page 物件來整理測試中取得頁面的元素,此一做法在 Cypress 也是適用的。不過,Cypress 建議使用自訂命令的方式來替代 Page 物件。透過自訂命令,我們可以自己把測試的使用者操作流程自定成一個命令,來在不同的測試案例中使用。

前置設定

在開始說明自訂命令之前,需要針對應用程式專案做兩個設定。首先,當我們初始化 Cypress 之後,在 cypress 資料夾有一個 support 目錄,用來放我們所自訂的命令檔案。這篇所實作的自訂命令會寫在 commands.ts 檔案內,為了讓 Cypress 可以載入到自訂命令,我們需要在 support/e2e.ts 檔案中匯入 commands.ts

// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
import './commands';

另外,在 Cypress 的組態檔中,supportFile 參數用來設定在取得測試檔案前,要被載入的檔案路徑,預設 cypress/support/e2e.{js,jsx,ts,tsx};當這個參數值設定為 false 時,代表 Cypress 不載入其他的檔案。

自訂命令新增

設定完 Cypress 之後,就可以來建立一個自訂命令。如上面所說明,我們在登入的三個測試案例中,都指定了輸入完帳號與密碼後,去點選登入按鈕,以及驗證結果的訊息。此時,我們就可以自訂 inputIdAndPasswordloginsnackBarShouldBe 三個命令來簡化測試案例的內容。

首先,我們在 commands.ts 裡,去針對 Cypress.Chainable<Subject> 介面新增 inputIdAndPasswordloginsnackBarShouldBe 三個命令簽章:

declare namespace Cypress {
  interface Chainable<Subject = any> {
    inputIdAndPassword(id: string, password: string): Chainable<JQuery<HTMLElement>>;
    login(): Chainable<JQuery<HTMLElement>>;
    snackBarShouldBe(message: string): Chainable<JQuery<HTMLElement>>;
  }
}

接著,就透過 Cypress.Commands 內的 add() 方法來新增自訂命令。這個方法會傳入命令名稱,以及命令方法主體兩個參數。

Cypress.Commands.add('inputIdAndPassword', (id: string, password: string) => {
  cy.get('input[type=text]').type(id).blur();
  if (password) {
    cy.get('input[type=password]').type(password);
  }
});

Cypress.Commands.add('login', () => {
  return cy.get('button').contains('登入').parent().click({ force: true });
});

Cypress.Commands.add('snackBarShouldBe', (message: string) =>
  cy.get('.mat-snack-bar-container').should('exist').and('have.text', message)
);

最後,我們就可以在測試案例中,直接使用自訂的 inputIdAndPasswordloginsnackBarShouldBe 三個命令。

describe('登入作業', () => {
  it('當輸入不存在帳號, 應顯示錯誤訊息, 且無法按下登入按鈕', () => {
    cy.fixture('users/id-not-exists').then(({ id, password }) => {
      cy.inputIdAndPassword(id, password);
      cy.login().should('be.disabled');
      cy.get('mat-error').should('exist').and('have.text', '此帳號不存在');
    });
  });

  it('當輸入錯誤帳號密碼, 應登入失敗', () => {
    cy.fixture('users/login-failed').then(({ id, password }) => {
      cy.inputIdAndPassword(id, password);
      cy.login().should('not.be.disabled');
      cy.snackBarShouldBe('登入失敗');
    });
  });

  it('當輸入正確帳號密碼, 應登入成功', () => {
    cy.fixture('users/login-success').then(({ id, password }) => {
      cy.inputIdAndPassword(id, password);
      cy.login().should('not.be.disabled');
      cy.snackBarShouldBe('登入成功');
    });
  });
});

接下來

今天說明了 Cypress 提供的 as() 方法與自訂命令,完整的測試程式可以參考 GitHub。接下來,會來說明在執行完 Cypress 端對端測試後如何產生報表。


上一篇
Day 22 - 端對端測試 - 使用 Fixture 定義測試資料與環境變數設定
下一篇
Day 24 - 端對端測試 - Cypress 報表設定
系列文
今天我想來在 Angular 應用程式上加上測試保護30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言